package intopo.game.impl.givetake;

import intopo.evogame.partnerselection.config.ParsingException;
import java.io.IOException;
import java.io.PrintStream;
import org.w3c.dom.Element;
import cern.jet.random.engine.RandomEngine;
import cern.jet.random.Binomial;

import intopo.evogame.partnerselection.Cache;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import util.xml.*;
import intopo.game.strategy.ConstantStrategyParameter;
import intopo.game.strategy.StrategyParameter;
import intopo.util.Util;
import java.util.Scanner;

/**
 * This class represents a Give-Take player without memory whose strategy
 * is probabilistic. This strategy has 6 parameters divided in two sets:
 * one is used when the player has the resource, while the other when the
 * player does not have it.

 * <p> When the player obtains the resource possession (his opponent give
 * it, he took it, or he started the game with the resource) he will wait
 * {@code noneGiterations} iterations before giving it. Afterwards it may
 * give the resource with probability {@code giveProbability}. If {@code
 * giveIteration} iterations have elapsed, then the player gives the
 * resource.
 *
 * <h3>Text Config File</h3>
 *
 * <p> The config file contains strategies tagged with strings.  All of them can be in the same line.  As examples we have:
 *
 * <pre>
 * GiveTakeDeterministicStrategy 1 4
 * GiveTakeDeterministicStrategy 4 1
 * GiveTakeDeterministicStrategy 5 5
 * </pre>

 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Lopes da Silva Mariano</a>

 * @version 2.0
 */
public class GiveTakeDeterministicStrategy
	extends GiveTakeStrategy
{
	/**
	 * Constructor tag used in text streams and in the map ADT.
	 */
	static final String CONSTRUCTOR_TAG = "GiveTakeDeterministicStrategy";
	/**
	 * How much time the player keeps the resource before it gives
	 * it. This field represents the number of iterations that must pass
	 * since the player obtained the resource possession before it may
	 * give. The lowest value is 0 meaning the player gives the resource
	 * rightly after it had had it. */
	protected int giveTime;

	/**
	 * How much time the player waits without the resource before it
	 * takes it. The lowest value is 0 meaning the player takes the resource
	 * rightly after it had not had it. */
	protected int takeTime;

	public GiveTakeDeterministicStrategy (int giveTime, int takeTime)
	{
		this.giveTime = giveTime;
		this.takeTime = takeTime;
	}

	/**
	 * Construct a new {@code GiveTakeProbabilisticPlayer} whose fields are
	 * initialized by searching the DOM element for specific child nodes.
	 * <p>The XML document must have the following tags and attributes:
	 * {@code <GiveTakeDeterministicStrategy>
	 * <GiveTime attr />
	 * <TakeTime attr />
	 * </GiveTakeDeterministicStrategy> }
	 * 
	 * <p> This constructor is used by class {@code game.PlayerGeneratorfromXML}
	 * 
	 * @see intopo.game.XMLStrategyGenerator
	 *
	 * @param element The XML element that contains the strategy parameters
	 * @param random
	 *
	 * @throws util.xml.XMLParseException If there is an error parsing the XML elements that represent the strategy parameters.
	 */
	public GiveTakeDeterministicStrategy (Element element, RandomEngine random)
		throws XMLParseException
	{
		AttributeParser<Integer> apTime = new AttributeParser<Integer>
			(new DefaultValue<Integer> (Integer.MAX_VALUE),
			 new ParseAsRandomInt (),
			 new ParseAsInt (0, Integer.MAX_VALUE));

		giveTime = apTime.parse (element, "GiveTime", "value");
		takeTime = apTime.parse (element, "TakeTime", "value");
	}
	/**
	 * Construct a Give-Take deterministic strategy from the data in the given scanner.  The scanner must contain two integers: the time the strategy keeps the resource before giving it and the time the strategy waits withouth the resource before taking it.
	 *
	 * <p> This constructor is used by game descriptions for text files.  The configuration file for a simulation run contains strategies tagged with strings.  The game description requires a constructor with an argument of type {@code java.util.Scanner}.
	 *
	 * <p> Both parameters and the tag can be in the same line.
	 * 
	 * @param scanner
	 */
	public GiveTakeDeterministicStrategy (Scanner scanner)
	{
		this (scanner.nextInt (), scanner.nextInt ());
	}

	public GiveTakeStrategy clone ()
	{
		return new GiveTakeDeterministicStrategy (this.giveTime, this.takeTime);
	}

	public int numberParameters ()
	{
		return 2;
	}

	public void setParameterValue (int parameterIndex, Object value)
	{
		switch (Parameter.values () [parameterIndex]) {
			case GIVE_TIME:
				giveTime = ((Number) value).intValue ();
				break;
			case TAKE_TIME:
				takeTime = ((Number) value).intValue ();
				break;
		default:
			throw new AssertionError ();
		}
	}

	public Object getParameterValue (int index)
	{
		switch (Parameter.values () [index]) {
			case GIVE_TIME:
				return new Integer (giveTime);
			case TAKE_TIME:
				return new Integer (takeTime);
		}
		throw new AssertionError ();
	}

	public String getParameterName (int index)
	{
		return Parameter.values () [index].name;
	}

	public StrategyParameter getParameter (int index)
	{
		switch (Parameter.values () [index]) {
			case GIVE_TIME:
				return new ConstantStrategyParameter<Integer> (Parameter.values () [index].name, giveTime);
			case TAKE_TIME:
				return new ConstantStrategyParameter<Integer> (Parameter.values () [index].name, takeTime);
		}
		throw new AssertionError ();
	}

	public boolean give (GiveTakeHistory gth, int playerIndex)
	{
		int time = gth.getIterationsSinceLastResourceExchange ();
		return time >= giveTime;
	}

	public boolean take (GiveTakeHistory gth, int playerIndex)
	{
		int time = gth.getIterationsSinceLastResourceExchange ();
		return time >= takeTime;
	}

	@Override
	public int averageGive ()
	{
		return giveTime;
	}

	@Override
	public int averageTake ()
	{
		return takeTime;
	}

	@Override
	public GiveTakeStrategy getMutantAllGenes ()
	{
		int givet;
		int taket;
		Binomial binomial;
		binomial = new Binomial (Parameters.giveIteration, Parameters.binomialSuccess, Cache.supply);
		if (Cache.supply.nextInt () < 0) {
			givet = Math.max (this.giveTime - binomial.nextInt (), 0);
		}
		else {
			givet = Math.min (this.giveTime + binomial.nextInt (), Parameters.giveIteration);
		}
		binomial = new Binomial (Parameters.takeIteration, Parameters.binomialSuccess, Cache.supply);
		if (Cache.supply.nextInt () < 0) {
			taket = Math.max (this.takeTime - binomial.nextInt (), 0);
		}
		else {
			taket = Math.min (this.takeTime + binomial.nextInt (), Parameters.takeIteration);
		}
		return new GiveTakeDeterministicStrategy (givet, taket);
	}

	@Override
	public GiveTakeStrategy getMutantSingleGene ()
	{
		int givet;
		int taket;
		int gene = Cache.supply.nextFloat() < 0.5 ? 0 : 1;
		Binomial binomial;
		if (gene == 0) {
			binomial = new Binomial (Parameters.giveIteration, Parameters.binomialSuccess, Cache.supply);
			if (Cache.supply.nextInt () < 0) {
				givet = Math.max (this.giveTime - binomial.nextInt (), 0);
			}
			else {
				givet = Math.min (this.giveTime + binomial.nextInt (), Parameters.giveIteration);
			}
		}
		else {
			givet = this.giveTime;
		}
		if (gene == 1) {
			binomial = new Binomial (Parameters.takeIteration, Parameters.binomialSuccess, Cache.supply);
			if (Cache.supply.nextInt () < 0) {
				taket = Math.max (this.takeTime - binomial.nextInt (), 0);
			}
			else {
				taket = Math.min (this.takeTime + binomial.nextInt (), Parameters.takeIteration);
			}
		}
		else {
			taket = this.takeTime;
		}
		return new GiveTakeDeterministicStrategy (givet, taket);
	}

	@Override
	public int getBin ()
	{
		int bin = 0, r;
		bin = this.giveTime * (Parameters.takeIteration + 1) + this.takeTime;
		return bin;
	}
	/**
	 * Return a description of a strategy that corresponds to the given bin.
	 * @param bin
	 * @return
	 */
	static String getBinDescription (int bin)
	{
		int giveTime = bin / (Parameters.takeIteration + 1) + 1;
		int takeTime = bin % (Parameters.takeIteration + 1) + 1;
		return String.format ("(%d, %d)", giveTime, takeTime);
	}
	
	@Override
	public boolean isCooperator ()
	{
		return this.giveTime < this.takeTime;
	}
	/**
	 * Writes the parameters of this game in the given text stream.  The written data can be read from the constructor with a parameter of type {@code Scanner}.
	 *
	 * @param ps
	 *
	 * @see #GiveTakeDeterministicStrategy(Scanner)
	 */
	@Override
	public void print (java.io.PrintStream ps)
	{
		ps.print (CONSTRUCTOR_TAG);
		ps.print (' ');
		ps.print (giveTime);
		ps.print (' ');
		ps.print (takeTime);
	}
	/**
	 * Writes this strategy to the given stream.
	 * <p> The data format is:
	 * <pre>
	 * | byte 1 | byte 2 |
	 * |  give  |  take  |
	 * |  time  |  time  |
	 * </pre>
	 * @param dos The stream where the data is written
	 * @throws java.io.IOException
	 */
	@Override
	public void write (java.io.DataOutputStream dos)
		throws IOException
	{
		dos.writeByte (this.giveTime);
		dos.writeByte (this.takeTime);
	}
	@Override
	public String toString ()
	{
		StringBuffer sb = new StringBuffer (2 * 10 + 1);
		sb.append (this.giveTime);
		sb.append (' ');
		sb.append (this.takeTime);
		return sb.toString ();
	}

	/**
	 * An enumeration that represents {@code GiveTakeProbabilisticPlayer} strategy parameters
	 */
	enum Parameter
	{
		GIVE_TIME ("givet"),
		TAKE_TIME ("taket");

		/**
		 * The name of the strategy parameter.
		 */
		private String name;

		/**
		 * Sole constructor.

		 * @param name The name of the strategy parameter.
		 */
		private Parameter (String name)
		{
			this.name = name;
		}
	}
	/**
	 * Provides static fields used in calculating the <i>bin</i> of the enclosing strategy and parameters that effect the outcome of a mutated strategy.
	 *
	 * <p> Some of the fields also define the maximum value a strategy parameter can have.
	 */
	static public class Parameters
	{
		/**
		 * Maximum range of the give parameter of the deterministic strategy.  This parameter is stored in field {@code GiveTakeDeterministicStrategy#giveTime}.
		 * @see intopo.game.impl.givetake.GiveTakeDeterministicStrategy#giveTime
		 */
		static public int giveIteration = 5;
		/**
		 * Maximum range of the take parameter of the deterministic strategy.  This parameter is stored in field {@code GiveTakeDeterministicStrategy#takeTime}.
		 * @see intopo.game.impl.givetake.GiveTakeDeterministicStrategy#takeTime
		 */
		static public int takeIteration = 5;
		/**
		 * Success probability of the binomial distribution used to mutate the parameters of the deterministic strategy.  The range of values of the binomial distribution is given by fields {@code giveIteration} and {@code takeIteration}.
		 * @see #giveIteration
		 * @see #takeIteration
		 * @see intopo.game.impl.givetake.GiveTakeDeterministicStrategy#getMutantAllGenes()
		 * @see intopo.game.impl.givetake.GiveTakeDeterministicStrategy#getMutantSingleGene()
		 */
		static public double binomialSuccess = 0.5;
		/**
		 * Reads global parameters that affect the behaviour of {@code GiveTakeDeterministicStrategy} instances.
		 * <p> The format of the data in the scanner is the following:
		 * <pre>
		 * |    integer    |    integer    |     integer     |
		 * | giveIteration | takeIteration | binomialSuccess |
		 * </pre>
		 * @param scanner
		 */
		static void read (Scanner scanner)
			throws ParsingException
		{
			Parameters.giveIteration = AbstractTextConfigFile.readInt (scanner, "Maximum range of giveTime strategy parameter");
			Parameters.takeIteration = AbstractTextConfigFile.readInt (scanner, "Maximum range of takeTime strategy parameter");
			Parameters.binomialSuccess = AbstractTextConfigFile.readFloat (scanner, "Success probability of the binomial distribution used to mutate the strategy parameters");
		}

		static void write (PrintStream ps)
		{
			ps.print (Parameters.giveIteration);
			ps.println ("\t# Maximum range of giveTime strategy parameter");
			ps.print (Parameters.takeIteration);
			ps.println ("\t# Maximum range of takeTime strategy parameter");
			ps.print (Parameters.binomialSuccess);
			ps.println ("\t# Success probability of the binomial distribution used to mutate the strategy parameters");
		}
	}
}
